﻿using DotNetCommon.Extensions;
using System;
using System.Buffers.Text;
using System.Buffers;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace DotNetCommon.Serialize
{
    #region DateOnly & TimeOnly
    /// <summary>
    /// 专为 .net6 提供
    /// </summary>
    internal sealed class JsonConverterDateOnly : System.Text.Json.Serialization.JsonConverter<DateOnly>
    {
        public string Format { get; set; }
        public JsonConverterDateOnly()
        {
            this.Format = "yyyy-MM-dd";
        }
        public JsonConverterDateOnly(string format)
        {
            if (format == null) throw new ArgumentNullException("format");
            Format = format;
        }
        public override DateOnly Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options)
        {
            return DateOnly.Parse(reader.GetString());
        }

        public override void Write(System.Text.Json.Utf8JsonWriter writer, DateOnly value, System.Text.Json.JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.ToString(Format));
        }
    }

    /// <summary>
    /// 专为 .net6 提供
    /// </summary>
    internal sealed class JsonConverterTimeOnly : System.Text.Json.Serialization.JsonConverter<TimeOnly>
    {
        public string Format { get; set; }
        public JsonConverterTimeOnly()
        {
            this.Format = "HH:mm:ss.fffffff";
        }
        public JsonConverterTimeOnly(string format)
        {
            if (format == null) throw new ArgumentNullException("format");
            Format = format;
        }
        public override TimeOnly Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options)
        {
            return TimeOnly.Parse(reader.GetString());
        }

        public override void Write(System.Text.Json.Utf8JsonWriter writer, TimeOnly value, System.Text.Json.JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.ToString(Format));

        }
    }
    #endregion

    #region Datetime & DateTimeOffset
    /// <summary>
    /// DateTime转换器
    /// </summary>
    internal class JsonConverterDatetime : System.Text.Json.Serialization.JsonConverter<DateTime>
    {
        /// <summary>
        /// 如: "yyyy-MM-dd HH:mm:ss",默认: null
        /// </summary>
        public string Format { get; set; }
        /// <summary>
        /// 使用默认格式,同: <c>System.Text.Json.JsonSerializer.Serialize(obj)</c>
        /// </summary>
        public JsonConverterDatetime() { }
        /// <summary>
        /// 指定格式化字符串
        /// </summary>
        public JsonConverterDatetime(string format)
        {
            Format = format;
        }
        /// <summary>
        /// 自定义反序列化方法
        /// </summary>
        public override DateTime Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options)
        {
            if (!reader.TryGetDateTime(out DateTime value))
            {
                value = DateTime.Parse(reader.GetString()!);
            }
            return value;
        }

        /// <summary>
        /// 自定义序列化方法
        /// </summary>
        public override void Write(System.Text.Json.Utf8JsonWriter writer, DateTime value, System.Text.Json.JsonSerializerOptions options)
        {
            if (Format.IsNotNullOrEmptyOrWhiteSpace())
            {
                writer.WriteStringValue(value.ToString(Format));
            }
            else
            {
                Span<byte> utf8Date = new byte[50];
                bool result = Utf8Formatter.TryFormat(value, utf8Date, out int len, new StandardFormat('O'));
                if (result) writer.WriteStringValue(utf8Date.Slice(0, len));
                else writer.WriteStringValue(value.ToString(Format));
            }
        }
    }

    /// <summary>
    /// DateTimeOffset转换器
    /// </summary>
    internal class JsonConverterDateTimeOffset : System.Text.Json.Serialization.JsonConverter<DateTimeOffset>
    {
        /// <summary>
        /// 如: "yyyy-MM-dd HH:mm:ss",默认: null
        /// </summary>
        public string Format { get; set; }
        /// <summary>
        /// 使用默认格式,同: <c>System.Text.Json.JsonSerializer.Serialize(obj)</c>
        /// </summary>
        public JsonConverterDateTimeOffset() { }
        /// <summary>
        /// 指定格式化字符串
        /// </summary>
        /// <param name="format"></param>
        public JsonConverterDateTimeOffset(string format)
        {
            Format = format;
        }
        /// <summary>
        /// 自定义反序列化方法
        /// </summary>
        public override DateTimeOffset Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options)
        {
            if (!reader.TryGetDateTimeOffset(out DateTimeOffset value))
            {
                value = DateTimeOffset.Parse(reader.GetString()!);
            }
            return value;
        }

        /// <summary>
        /// 自定义序列化方法
        /// </summary>
        public override void Write(System.Text.Json.Utf8JsonWriter writer, DateTimeOffset value, System.Text.Json.JsonSerializerOptions options)
        {
            if (Format.IsNotNullOrEmptyOrWhiteSpace())
            {
                writer.WriteStringValue(value.ToString(Format));
            }
            else
            {
                Span<byte> utf8Date = new byte[50];
                bool result = Utf8Formatter.TryFormat(value, utf8Date, out int len, new StandardFormat('O'));
                if (result) writer.WriteStringValue(utf8Date.Slice(0, len));
                else writer.WriteStringValue(value.ToString(Format));
            }
        }
    }
    #endregion

    /// <summary>
    /// string转换器,解决问题: <br />
    /// 123 => "123"<br/>
    /// true => "true"
    /// </summary>
    internal class JsonConverterString : System.Text.Json.Serialization.JsonConverter<string>
    {
        /// <summary>
        /// 空参构造函数
        /// </summary>
        public JsonConverterString() { }
        /// <summary>
        /// 自定义反序列化方法
        /// </summary>
        public override string Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options)
        {
            switch (reader.TokenType)
            {
                case JsonTokenType.None:
                case JsonTokenType.Null:
                    return null;
                case System.Text.Json.JsonTokenType.Number:
                    return reader.GetDouble().ToString();
                case System.Text.Json.JsonTokenType.True:
                    return "true";
                case System.Text.Json.JsonTokenType.False:
                    return "false";
                default:
                    return reader.GetString();
            }
        }

        /// <summary>
        /// 自定义序列化方法
        /// </summary>
        public override void Write(System.Text.Json.Utf8JsonWriter writer, string value, System.Text.Json.JsonSerializerOptions options)
        {
            writer.WriteStringValue(value);
        }
    }

    internal class JsonConverterBool : System.Text.Json.Serialization.JsonConverter<bool>
    {
        public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            switch (reader.TokenType)
            {
                case JsonTokenType.None:
                case JsonTokenType.Null:
                    throw new Exception($"无法将 null 反序列化为 bool!");
                case JsonTokenType.Number:
                    var d = reader.GetDouble();
                    return !(d == 0);
                case JsonTokenType.String:
                    var str = reader.GetString();
                    if (string.Equals(str, "true", StringComparison.OrdinalIgnoreCase)) return true;
                    else if (string.Equals(str, "false", StringComparison.OrdinalIgnoreCase)) return false;
                    else throw new Exception($"无法将非 \"true\"或\"false\" 的字符串 转为 bool!");
                case JsonTokenType.True: return true;
                case JsonTokenType.False: return false;
                default: throw new Exception($"无法将 {reader.TokenType} 反序列化为 bool!");
            }
        }

        public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
        {
            writer.WriteBooleanValue(value);
        }
    }
    internal class NullAbleConverter : JsonConverterFactory
    {
        private static ConcurrentDictionary<Type, JsonConverter> _cache = new ConcurrentDictionary<Type, JsonConverter>();
        public override bool CanConvert(Type typeToConvert)
        {
            return typeToConvert.IsNullable();
        }

        public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
        {
            return _cache.GetOrAdd(typeToConvert, () =>
            {
                return (JsonConverter)Activator.CreateInstance(typeof(NullAbleConverter<>).MakeGenericType(typeToConvert));
            });
        }
    }

    internal class NullAbleConverter<T> : JsonConverter<T>
    {
        public NullAbleConverter()
        {
            s_UnderlyingType = Nullable.GetUnderlyingType(typeof(T));
        }
        private Type s_UnderlyingType = null;
        public override bool CanConvert(Type typeToConvert)
        {
            return typeToConvert.IsNullable();
        }
        public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            switch (reader.TokenType)
            {
                case JsonTokenType.Null:
                case JsonTokenType.None:
                    return default(T);
                case JsonTokenType.String:
                    var str = reader.GetString();
                    if (str == string.Empty) return default(T);
                    return (T)JsonSerializer.Deserialize(ref reader, s_UnderlyingType, options);
                default:
                    return (T)JsonSerializer.Deserialize(ref reader, s_UnderlyingType, options);
            }
        }

        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
        {
            if (value == null)
            {
                writer.WriteStringValue("null");
            }
            else
            {
                JsonSerializer.Serialize(writer, value, value.GetType(), options);
            }
        }
    }

    internal class JsonEnumConverter : JsonConverterFactory
    {
        public JsonEnumConverter(bool enum2String)
        {
            this.enum2String = enum2String;
        }
        private static ConcurrentDictionary<Type, JsonConverter> _cache2String = new ConcurrentDictionary<Type, JsonConverter>();
        private static ConcurrentDictionary<Type, JsonConverter> _cache2Numer = new ConcurrentDictionary<Type, JsonConverter>();
        private readonly bool enum2String;

        public sealed override bool CanConvert(Type typeToConvert)
        {
            return typeToConvert.IsEnum;
        }

        public sealed override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
        {
            var _cache = enum2String ? _cache2String : _cache2Numer;
            return _cache.GetOrAdd(typeToConvert, () =>
            {
                var ctor = typeof(EnumConverter<>).MakeGenericType(typeToConvert).GetConstructor(new Type[] { typeof(bool) });
                return (JsonConverter)ctor.Invoke(new object[] { enum2String });
            });
        }
    }

    internal sealed class EnumConverter<T> : JsonConverter<T> where T : struct, Enum
    {
        public EnumConverter(bool enum2String)
        {
            this.enum2String = enum2String;
        }
        private static readonly TypeCode s_enumTypeCode = Type.GetTypeCode(typeof(T));
        private readonly bool enum2String;

        public override bool CanConvert(Type type)
        {
            return type.IsEnum;
        }

        public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            switch (reader.TokenType)
            {
                case JsonTokenType.String:
                    var str = reader.GetString();
                    return (T)Enum.Parse(typeof(T), str, true);
                case JsonTokenType.Number:
                    switch (s_enumTypeCode)
                    {
                        case TypeCode.Int32:
                            {
                                if (reader.TryGetInt32(out var value8))
                                {
                                    return Unsafe.As<int, T>(ref value8);
                                }
                                break;
                            }
                        case TypeCode.UInt32:
                            {
                                if (reader.TryGetUInt32(out var value4))
                                {
                                    return Unsafe.As<uint, T>(ref value4);
                                }
                                break;
                            }
                        case TypeCode.UInt64:
                            {
                                if (reader.TryGetUInt64(out var value6))
                                {
                                    return Unsafe.As<ulong, T>(ref value6);
                                }
                                break;
                            }
                        case TypeCode.Int64:
                            {
                                if (reader.TryGetInt64(out var value2))
                                {
                                    return Unsafe.As<long, T>(ref value2);
                                }
                                break;
                            }
                        case TypeCode.SByte:
                            {
                                if (reader.TryGetSByte(out var value7))
                                {
                                    return Unsafe.As<sbyte, T>(ref value7);
                                }
                                break;
                            }
                        case TypeCode.Byte:
                            {
                                if (reader.TryGetByte(out var value5))
                                {
                                    return Unsafe.As<byte, T>(ref value5);
                                }
                                break;
                            }
                        case TypeCode.Int16:
                            {
                                if (reader.TryGetInt16(out var value3))
                                {
                                    return Unsafe.As<short, T>(ref value3);
                                }
                                break;
                            }
                        case TypeCode.UInt16:
                            {
                                if (reader.TryGetUInt16(out var value))
                                {
                                    return Unsafe.As<ushort, T>(ref value);
                                }
                                break;
                            }
                    }
                    throw new Exception($"无法从 {JsonTokenType.Number} 转为枚举!");
                default:
                    throw new Exception($"无法从 {reader.TokenType} 转为枚举!");
            }
        }

        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
        {
            if (enum2String)
            {
                writer.WriteStringValue(value.ToString());
            }
            else
            {
                switch (s_enumTypeCode)
                {
                    case TypeCode.Int32:
                        writer.WriteNumberValue(Unsafe.As<T, int>(ref value));
                        break;
                    case TypeCode.UInt32:
                        writer.WriteNumberValue(Unsafe.As<T, uint>(ref value));
                        break;
                    case TypeCode.UInt64:
                        writer.WriteNumberValue(Unsafe.As<T, ulong>(ref value));
                        break;
                    case TypeCode.Int64:
                        writer.WriteNumberValue(Unsafe.As<T, long>(ref value));
                        break;
                    case TypeCode.Int16:
                        writer.WriteNumberValue(Unsafe.As<T, short>(ref value));
                        break;
                    case TypeCode.UInt16:
                        writer.WriteNumberValue(Unsafe.As<T, ushort>(ref value));
                        break;
                    case TypeCode.Byte:
                        writer.WriteNumberValue(Unsafe.As<T, byte>(ref value));
                        break;
                    case TypeCode.SByte:
                        writer.WriteNumberValue(Unsafe.As<T, sbyte>(ref value));
                        break;
                    default:
                        throw new Exception($"无法将 {s_enumTypeCode} 序列化!");
                }
            }
        }
    }
}
